﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using AutoCorrelatorGUI.Model;
using AutoCorrelatorGUI.Properties;
using AutoCorrelatorGUI.Utility;
using AutoCorrelatorGUI.ViewModel.Messages;
using Common.Utility;
using Common.ViewModel;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Ioc;
using GalaSoft.MvvmLight.Messaging;
using OxyPlot;
using OxyPlot.Series;

namespace AutoCorrelatorGUI.ViewModel
{
   [Serializable]
   public enum FourierLineStyles
   {
      [Description("Solid Line")]
      Line,
      [Description("Solid Line With Markers")]
      LineAndMarker,
      [Description("Stair Step")]
      StairStep,
      [Description("Stair Step With Markers")]
      StairStepAndMarker,
   }

   /// <summary>
   /// This class contains properties that a View can data bind to.
   /// <para>
   /// See http://www.galasoft.ch/mvvm
   /// </para>
   /// </summary>
   public class FourierDisplayViewModel : ViewModelBase
   {
      private static MarkerType[] _MarkerPalette = new MarkerType[4] { MarkerType.Circle, MarkerType.Square, MarkerType.Diamond, MarkerType.Triangle };

      private Dictionary<string, LineSeries> _TitleToSeries = new Dictionary<string, LineSeries>();
      private double _FreqSpacing = 1.0;

      /// <summary>
      /// Initializes a new instance of the MvvmViewModel1 class.
      /// </summary>
      public FourierDisplayViewModel()
      {
         Messenger.Default.Register<FullRefreshMessage>(this, OnFullRefresh);
         Messenger.Default.Register<DataUpdateMessage>(this, OnDataUpdate);

         PlotViewModel = new PlotModel() { Title = "Fourier Transform" };

         var xAxis = new OxyPlot.Axes.LinearAxis()
         {
            Position = OxyPlot.Axes.AxisPosition.Bottom,
            Key = "xAxis",
            MajorGridlineStyle = LineStyle.Solid,
            MinorGridlineStyle = LineStyle.None,
            IntervalLength = 50,
            MinorStep = 100,
            Title = "Frequency (Hz)"
         };

         var yAxis = new OxyPlot.Axes.LinearAxis()
         {
            Position = OxyPlot.Axes.AxisPosition.Left,
            Key = "yAxis",
            MajorGridlineStyle = LineStyle.Solid,
            MinorGridlineStyle = LineStyle.None,
            IntervalLength = 50,
            MinorStep = 100,
            Title = "Power (dB)"
         };

         PlotViewModel.Axes.Add(xAxis);
         PlotViewModel.Axes.Add(yAxis);

         PlotViewModel.IsLegendVisible = true;

         PlotViewModel.InvalidatePlot(true);

         //Request any existing data
         Messenger.Default.Send(new RequestDataMessage(OnFullRefresh));
      }

      private MultiSelectBindingList<WellVM> _InternalWellData = null;

      public MultiSelectBindingList<WellVM> InternalWellData
      {
         get { return _InternalWellData; }
         set
         {
            //Need to move the event handlers
            if (_InternalWellData != null)
            {
               _InternalWellData.ListChanged -= OnItemChanged;
            }

            _InternalWellData = value;

            if (_InternalWellData != null)
            {
               _InternalWellData.ListChanged += OnItemChanged;
            }

            RaisePropertyChanged("InternalWellData");
         }
      }

      public PlotModel PlotViewModel
      {
         get;
         private set;
      }

      public IEnumerable<KeyValuePair<Enum, string>> LineStyleOptionList
      {
         get
         {
            return EnumHelper.GetAllValuesAndDescriptions<FourierLineStyles>();
         }
      }

      /// <summary>
      /// The <see cref="NormalizationChoice" /> property's name.
      /// </summary>
      public const string NormalizationChoicePropertyName = "NormalizationChoice";

      private FourierLineStyles _LineStyleChoice = FourierLineStyles.StairStep;

      /// <summary>
      /// Sets and gets the NormalizationChoice property.
      /// Changes to that property's value raise the PropertyChanged event. 
      /// </summary>
      public FourierLineStyles LineStyleChoice
      {
         get
         {
            return _LineStyleChoice;
         }

         set
         {
            if (_LineStyleChoice == value)
            {
               return;
            }

            _LineStyleChoice = value;
            RaisePropertyChanged(NormalizationChoicePropertyName);

            CreateAllSeries();
            SyncAllProps();
            SyncAllData();
         }
      }

      private void OnFullRefresh(FullRefreshMessage message)
      {
         InternalWellData = message.DataView.SourceCollection as MultiSelectBindingList<WellVM>;

         _FreqSpacing = 1.0;

         //Determine the spacing
         if (InternalWellData != null && InternalWellData.Count > 0)
         {
            Well_Transform t = (InternalWellData[0] as WellVM).Transform;

            if (t != null && t.FourierData.Count > 1)
            {
               _FreqSpacing = t.FourierData[1].X - t.FourierData[0].X;
            }
         }

         CreateAllSeries();
         SyncAllProps();
         SyncAllData();
      }

      private void OnDataUpdate(DataUpdateMessage message)
      {
         if (message.UpdateType == DataUpdateTypes.Transform)
         {
            SyncAllData();

            foreach (var axis in PlotViewModel.Axes)
            {
               axis.Reset();
            }
         }
      }

      private void OnItemChanged(object sender, ListChangedEventArgs e)
      {
         SyncAllProps();
      }

      private LineSeries CreateSeries(int index)
      {
         LineSeries series = null;

         switch (_LineStyleChoice)
         {
         case FourierLineStyles.Line:
            series = new LineSeries();
            break;
         case FourierLineStyles.LineAndMarker:
            series = new LineSeries() { MarkerType = _MarkerPalette[index % _MarkerPalette.Length] };
            break;
         case FourierLineStyles.StairStep:
            series = new StairStepSeries();
            break;
         case FourierLineStyles.StairStepAndMarker:
            series = new StairStepSeries() { MarkerType = _MarkerPalette[index % _MarkerPalette.Length] };
            break;
         default:
            Debug.Fail("Unknown Line Style");
            break;
         }

         return series;
      }

      private void CreateAllSeries()
      {
         //Clear out all of the series and create new ones for each Well
         PlotViewModel.Series.Clear();
         _TitleToSeries.Clear();

         for (int i = 0; i < InternalWellData.Count; i++)
         {
            var series = CreateSeries(i);
            _TitleToSeries[InternalWellData[i].Title] = series;

            PlotViewModel.Series.Add(series);
         }
      }

      private void SyncAllProps()
      {
         for (int i = 0; i < InternalWellData.Count; i++)
         {
            SyncWellPropsToSeries(InternalWellData[i] as WellVM, _TitleToSeries[InternalWellData[i].Title]);
         }

         PlotViewModel.InvalidatePlot(true);
      }

      private void SyncWellPropsToSeries(WellVM well, LineSeries series)
      {
         series.Title = well.Title;
         series.IsVisible = well.Visible;

         OxyColor color = OxyColor.FromArgb(well.Color.A, well.Color.R, well.Color.G, well.Color.B);

         switch (well.Focus)
         {
         case FocusLevels.Low:
            SeriesSetColorSize(series, OxyColor.FromAColor((byte)(well.Color.A / 10), color), 2.0);
            break;
         case FocusLevels.Normal:
            SeriesSetColorSize(series, color, 2.0);
            break;
         case FocusLevels.High:
            SeriesSetColorSize(series, color, 4.0);
            break;
         default:
            break;
         }
      }

      void SeriesSetColorSize(LineSeries series, OxyColor color, double size)
      {
         series.Color = color;
         series.MarkerFill = color;
         series.MarkerStroke = color;
         series.StrokeThickness = size;
         
         series.MarkerSize = 1.0 + size;
         series.MarkerStrokeThickness = 1.0;

         if (_LineStyleChoice == FourierLineStyles.StairStep || _LineStyleChoice == FourierLineStyles.StairStepAndMarker)
         {
            StairStepSeries stairSeries = series as StairStepSeries;

            stairSeries.VerticalStrokeThickness = size / 2.0;
            stairSeries.VerticalLineStyle = LineStyle.Dot;
         }
      }

      private void SyncAllData()
      {
         for (int i = 0; i < InternalWellData.Count; i++)
         {
            SyncWellDataToSeries(InternalWellData[i] as WellVM, _TitleToSeries[InternalWellData[i].Title]);
         }

         PlotViewModel.InvalidatePlot(true);
      }

      private void SyncWellDataToSeries(WellVM well, LineSeries series)
      {
         series.Points.Clear();

         if (_LineStyleChoice == FourierLineStyles.Line || _LineStyleChoice == FourierLineStyles.LineAndMarker)
         {
            foreach (var point in well.Transform.FourierData)
            {
               series.Points.Add(new DataPoint(point.X + _FreqSpacing / 2.0, point.Y));
            }
         }
         else if (_LineStyleChoice == FourierLineStyles.StairStep || _LineStyleChoice == FourierLineStyles.StairStepAndMarker)
         {
            series.Points.AddRange(well.Transform.FourierData);
            DataPoint end = well.Transform.FourierData[well.Transform.FourierData.Count - 1];
            series.Points.Add(new DataPoint(end.X + _FreqSpacing, end.Y)); //Duplicate the last point to better show the last value
         }
         else
         {
            Debug.Fail("Unknown Line Style");
         }
      }
   }
}
